package net.xiaoboli.mgp;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.Version;
import lombok.SneakyThrows;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.logging.Log;
import org.mybatis.generator.logging.LogFactory;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Paths;
import java.util.*;

import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
import static org.mybatis.generator.internal.util.messages.Messages.getString;

/**
 * 模板插件
 * 执行顺序参考
 * http://www.mybatis.org/generator/reference/pluggingIn.html
 */
public class ViewPlugin extends PluginAdapter {

    private Log log;

    @Override
    @SneakyThrows
    public void initialized(IntrospectedTable introspectedTable) {
        this.log = LogFactory.getLog(this.getClass());
        try {
            this.doInitialized(introspectedTable);
        } catch (Exception exception) {
            this.log.error(exception.getMessage(), exception);
            throw exception;
        }
    }

    private void doInitialized(IntrospectedTable introspectedTable) throws TemplateException, IOException {
        // 项目目录，一般为 src/main/java
        String name = properties.getProperty("name");
        String nameAs = properties.getProperty("nameAs", "");
        String target = properties.getProperty("target");
        String _package = properties.getProperty("package", "");
        String ignoreFields = properties.getProperty("ignoreFields", "");
        String viewBase = properties.getProperty("view", "src/main/resources/mgp");
        viewBase = viewBase.trim();
        if (viewBase.endsWith("/"))
            viewBase = viewBase.substring(0, viewBase.length() - 1);
        if (viewBase.endsWith("\\"))
            viewBase = viewBase.substring(0, viewBase.length() - 1);

        //
        if (StringUtils.isBlank(name)) {
            log.warn("please set name property to ViewPlugin");
            return;
        }

        //
        String group = properties.getProperty("group", "");
        if (StringUtils.isBlank(group)) {
            log.warn("please set group property to ViewPlugin " + name);
            return;
        }

//        // global config
//        String globalCfg = properties.getProperty(group, "");
//        if ("no".equals(globalCfg)) {
//            log.warn("not make " + group + " " + name + " because " + group + " global config is no");
//            return;
//        }
//
//        // table config
//        String cfg = introspectedTable.getTableConfiguration().getProperty(group);
//        if ("no".equals(cfg)) {
//            log.warn("not make " + group + " " + name + " because table config " + group + " no");
//            return;
//        }

        //
        Set<String> ignoreFieldsSet = new HashSet<>();
        for (String s : ignoreFields.split(",")) {
            ignoreFieldsSet.add(s);
        }

        //
        String recordType = introspectedTable.getBaseRecordType();
        String modelName = recordType.substring(recordType.lastIndexOf(".") + 1);
        String tableName = introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime();
        String tablePath = tableName.replace("_", "/");

        //
        if (StringUtils.isNotBlank(_package)) {
            _package = PluginUtil.packageName(introspectedTable, _package);
        }


        String middlePath;
        // 有 package 使用 package
        if (StringUtils.isNotBlank(_package)) {
            middlePath = _package.replace(".", "/");
        }
        // 没有package，使用 tablePath
        else {
            middlePath = tablePath;
        }

        // 构建 output path
        String outputPath = Paths.get(target, middlePath, name).toString();
        if (StringUtils.isNotBlank(nameAs)) {
            if (nameAs.indexOf("{model}") > -1)
                outputPath = Paths.get(target, middlePath, nameAs.replace("{model}", modelName)).toString();
            else
                outputPath = Paths.get(target, middlePath, nameAs).toString();
        }


        //File viewFile = new File("views/" + name);
        File viewFile = new File(viewBase + "/" + name);

        //
        this.debug("tableName: " + tableName);
        this.debug("tablePath: " + tablePath);
        this.debug("recordType: " + recordType);
        this.debug("modelName: " + modelName);
        this.debug("outputPath: " + outputPath);

        //
        File targetFile = Paths.get(target).toFile();
        if (!targetFile.exists()) {
            log.warn("skip create because no exist target path: " + targetFile.getCanonicalPath());
            return;
        }

        //
        // <!-- 视图生成规则，no 不生成视图, 其它以逗号分隔的视图名称列表指定需要生成的视图列表 -->
        // <!-- <property name="view" value="no"/> -->
        this.debug(group + ": " + introspectedTable.getTableConfiguration().getProperty(group));
        if (introspectedTable.getTableConfiguration().getProperty(group) == null) {
            // make
        } else if ("yes".equals(introspectedTable.getTableConfiguration().getProperty(group))) {
            // make
        } else if ("no".equals(context.getProperty(group))) {
            // log.warn(viewFile.getCanonicalPath() + " skip " + introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime() + " create because global configured no");
            log.warn(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime() + " " + group + " " + name + " skip create because global configured no");
            return;
        } else if ("no".equals(introspectedTable.getTableConfiguration().getProperty(group))) {
            // log.warn(viewFile.getCanonicalPath() + " skip " + introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime() + " create because table configured no");
            log.warn(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime() + " " + group + " " + name + " skip create because table configured no");
            return;
        } else {
            String[] views = introspectedTable.getTableConfiguration().getProperty(group).split(",");
            Set<String> viewSet = new HashSet<>();
            viewSet.addAll(Arrays.asList(views));
            if (!viewSet.contains(name)) {
                // log.warn(viewFile.getCanonicalPath() + " skip " + introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime() + " create because table configured is no");
                log.warn(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime() + " " + group + " " + name + " skip create because table configured is no");
                return;
            }
        }

        // override
        if (new File(outputPath).exists()) {

            // <!-- 视图生成时执行覆盖规则的，不设置依据全局设置，all 覆盖所有视图列表，其它以逗号分隔的视图名称列表指定覆盖的视图列表 -->
            // <!-- <property name="viewOverride" value=""/> -->
            boolean isOverride = false;
            if (introspectedTable.getTableConfiguration().getProperty("viewOverride") == null) {
                //use global
                isOverride = "yes".equals(properties.getProperty("override", "no"));
            } else if ("all".equals(introspectedTable.getTableConfiguration().getProperty("viewOverride"))) {
                isOverride = true;
            } else {
                String[] overrides = introspectedTable.getTableConfiguration().getProperty("viewOverride").split(",");
                Set<String> overrideSet = new HashSet<>();
                overrideSet.addAll(Arrays.asList(overrides));
                isOverride = overrideSet.contains(name);
            }

            //
            if (!isOverride) {
                log.warn(Paths.get(outputPath).toFile().getCanonicalPath() + " is exists, skip create, you can delete it and regenerate");
                return;
            }
        }

        //
        this.debug("view: " + viewFile.getCanonicalPath());
        if (viewFile.exists() == false) {
            log.error("view " + viewFile.getCanonicalPath() + " is not exists, skip create");
            return;
        }

        //
        this.debug("tableMap");
        Map<String, Object> tableMap = new HashMap<>();
        Properties properties1 = introspectedTable.getTableConfiguration().getProperties();
        properties1.forEach((a, b) -> {
            tableMap.put((String) a, b);
        });
        //
        this.debug("pluginMap");
        Map<String, Object> pluginMap = new HashMap<>();
        properties.forEach((a, b) -> {
            pluginMap.put((String) a, b);
        });
        //
        this.debug("viewMap");
        Set<String> javaSet = new HashSet<>();
        Map<String, Object> viewMap = new HashMap<>();
        viewMap.put("nameAs", nameAs);
        viewMap.put("modelName", modelName);
        viewMap.put("recordType", recordType);
        viewMap.put("tableName", tableName);
        viewMap.put("tablePath", tablePath);
        viewMap.put("outputPath", outputPath);
        viewMap.put("javaTypes", javaSet);
        viewMap.put("packageName", _package);
        //
        this.debug("entity");
        List<ViewEntity> entities = new ArrayList<>();
        List<ViewEntity> indexEntities = new ArrayList<>();
        Set<String> indexColumns = PluginUtil.getIndexColumns(introspectedTable);
        List<IntrospectedColumn> allColumns = introspectedTable.getAllColumns();
        int index = -1;
        for (IntrospectedColumn column : allColumns) {
            index++;
            if (ignoreFieldsSet.contains(column.getActualColumnName())) continue;
            //
            ViewEntity entity = this.buildEntity(index, column, javaSet);
            //
            entities.add(entity);
            if (indexColumns.contains(column.getActualColumnName())) {
                indexEntities.add(entity);
            }
        }
        //
        this.debug("view data");
        Map<String, Object> data = new HashMap<>();
//        data.put("view", viewMap);
        data.put("table", tableMap);
        data.put("remark", introspectedTable.getRemarks());
        data.put("plugin", pluginMap);
        data.put("columns", entities);
        data.put("indexs", indexEntities);
        data.putAll(viewMap);
        //
        List<IntrospectedColumn> primaryKeyColumns = introspectedTable.getPrimaryKeyColumns();
        for (IntrospectedColumn column : primaryKeyColumns) {
            ViewEntity entity = this.buildEntity(0, column, javaSet);
            data.put("key", entity);
            break;
        }

        //
        Configuration cfg = new Configuration(new Version("2.3.23"));
        cfg.setDirectoryForTemplateLoading(viewFile.getParentFile());
        cfg.setDefaultEncoding("UTF-8");
        try {
            this.debug("parent");
            File parentFile = new File(outputPath).getParentFile();
            if (!parentFile.exists()) {
                this.info("mkdirs: " + parentFile.getCanonicalPath());
                parentFile.mkdirs();
            }
            //
            this.info("View " + viewFile.getCanonicalPath());
            Template template = cfg.getTemplate(viewFile.getName());
            try (StringWriter out = new StringWriter()) {
                template.process(data, out);
                out.flush();
                FileUtils.fileWrite(new File(outputPath), "UTF-8", out.getBuffer().toString());
            }
        } catch (Exception e) {
            log.error(e.toString());
            throw e;
        }

        this.info("Store " + new File(outputPath).getCanonicalPath());

    }

    private ViewEntity buildEntity(int index, IntrospectedColumn column, Set<String> javaSet) {
        ViewEntity entity = new ViewEntity();
        entity.setIndex(index);
        entity.setName(column.getActualColumnName());
        entity.setCamelName(column.getJavaProperty());
        entity.setGetter("get" + String.valueOf(column.getJavaProperty().charAt(0)).toUpperCase() + column.getJavaProperty().substring(1));
        entity.setDataType(column.getJdbcTypeName());
        if (column.getRemarks() == null)
            entity.setRemark(column.getActualColumnName());
        else if (column.getRemarks().trim().equals(""))
            entity.setRemark(column.getActualColumnName());
        else
            entity.setRemark(column.getRemarks().trim());

        entity.setType(column.getFullyQualifiedJavaType().getShortName());
        if (column.getFullyQualifiedJavaType().isExplicitlyImported()) {
            javaSet.add(column.getFullyQualifiedJavaType().getFullyQualifiedName());
        }

        if (column.getFullyQualifiedJavaType().isPrimitive()) {
            entity.setValue(column.getDefaultValue());
        } else if (column.isStringColumn()) {
            if (column.getDefaultValue() == null)
                entity.setValue("''");
            else
                entity.setValue("'" + column.getDefaultValue() + "'");
        } else {
            entity.setValue(column.getDefaultValue());
        }

        //
        if (column.getFullyQualifiedJavaType().getFullyQualifiedName().equals("java.util.Date")) {
            entity.setIsTime(1);
        }

        //
        entity.setIsIncrement(column.isAutoIncrement() ? 1 : 0);
        return entity;
    }

    @Override
    public boolean validate(List<String> warnings) {
        boolean valid = true;

        if (!stringHasValue(properties.getProperty("target"))) {
            warnings.add(getString("ValidationError.18",
                    this.getClass().getTypeName(),
                    "target"));
            valid = false;
        }

        if (!stringHasValue(properties.getProperty("name"))) {
            warnings.add(getString("ValidationError.18",
                    this.getClass().getTypeName(),
                    "name"));
            valid = false;
        }

        return valid;
    }

    private void debug(String msg) {
        if ("yes".equals(properties.getProperty("debug", "no"))) {
            System.out.println("[DEBUG] " + msg);
        }
    }

    private void info(String msg) {
        System.out.println("[INFO] ViewPlugin " + msg);
    }
}
